package com.vf.cloud.paas.app.agent.common.client;

import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.JsonKit;
import com.jfinal.kit.StrKit;
import com.vf.cloud.paas.app.agent.common.client.hander.Handler;
import com.vf.cloud.paas.app.agent.common.util.GpuUtil;
import com.vf.cloud.paas.app.agent.mvc.service.IRender;
import com.vf.cloud.paas.common.cirrus.Cirrus;
import com.vf.cloud.paas.common.vo.GpuInfo;
import com.vf.cloud.paas.common.vo.OsInfo;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class RenderClient {

	@Autowired
	private IRender renderImpl;

	@Value("${agent.token}")
	private String agentToken;

	@Value("${agent.master.ip}")
	private String mRenderIP;

	@Value("${agent.master.dispatch.port}")
	private String mRenderPort;

	@Value("${agent.install.path}")
	private String installPath;

	private EventLoopGroup group;
	private Channel channel;
	private boolean isDestroy = false;

	@Async
	public void run() {

		if (isDestroy) {
			return;
		}

		Cirrus.isRenderClientConnected = false;
		Cirrus.isRenderClientConnectedCompleted = false;

		if (StrKit.isBlank(agentToken) || StrKit.isBlank(mRenderIP) || StrKit.isBlank(mRenderPort)) {
			Cirrus.isRenderClientConnected = false;
			Cirrus.isRenderClientConnectedCompleted = true;
			log.info(">>> RenderClient:Token is null ." );
			return;
		}
		start();
	}

	private void start() {
		try {
			log.info(String.format(">>> RenderClient:正在连接至主节点[%s]." ,mRenderPort));
			Handler handler = new Handler(renderImpl);
			Bootstrap bootstrap = new Bootstrap();
			group = new NioEventLoopGroup();
			bootstrap.group(group);
			bootstrap.channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
					.option(ChannelOption.SO_KEEPALIVE, true).handler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ChannelPipeline pipeline = ch.pipeline();
							pipeline.addLast(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
							pipeline.addLast(new HttpClientCodec());
							pipeline.addLast(new ChunkedWriteHandler());
							pipeline.addLast(new HttpObjectAggregator(1024 * 64));
							pipeline.addLast(handler);

						}
					});

			URI websocketURI = new URI(String.format("ws://%s:%s/%s", mRenderIP, mRenderPort, agentToken));
			HttpHeaders httpHeaders = new DefaultHttpHeaders();
			// 进行握手
			WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(websocketURI,
					WebSocketVersion.V13, (String) null, true, httpHeaders);
			channel = bootstrap.connect(websocketURI.getHost(), websocketURI.getPort()).sync().channel();
			handler.setHandshaker(handshaker);
			handshaker.handshake(channel);
			// 阻塞等待是否握手成功
			handler.handshakeFuture().sync();
			Cirrus.isRenderClientConnected = true;
			Cirrus.isRenderClientConnectedCompleted = true;
			log.info(String.format(">>> RenderClient:连接成功[%s]." ,mRenderPort));

			if (channel != null && channel.closeFuture() != null) {
				channel.closeFuture().sync();
			}
		} catch (Exception e) {
		} finally {
			if (channel != null)
				channel.close();
			group.shutdownGracefully();
			Cirrus.isRenderClientConnected = false;
			Cirrus.isRenderClientConnectedCompleted = true;
			log.info(String.format(">>> RenderClient:释放连接[%s]." ,mRenderPort));
		}
	}

	public void sendGpuInfos(JSONArray array) {
		Cirrus.agentToken = agentToken;
		log.info(">>> RenderClient:Getting nvidia graphics information ...");
		if (Cirrus.isRenderClientConnected) {
			List<GpuInfo> gpuInfos = GpuUtil.getGpuInfos(installPath);
			Map<String, Object> gpusConfig;
			for (GpuInfo g : gpuInfos) {
				for (int i = 0; i < array.size(); i++) {
					JSONObject a = (JSONObject) array.get(i);
					String id = a.getString("id");
					int index = a.getIntValue("index");
					int limit = a.getIntValue("limit");
					if(StrKit.equals(g.getBoardId(),id) && index==Integer.parseInt(g.getModuleId())) {
						if (limit <= 0) {
							gpusConfig = new HashMap<String, Object>();
							gpusConfig.put("type", "GPU_Init");
							gpusConfig.put("token", agentToken);
							gpusConfig.put("uuid", g.getBoardId());
							gpusConfig.put("index", g.getModuleId());
							gpusConfig.put("sn", 0);
							gpusConfig.put("time", System.currentTimeMillis());
							send(gpusConfig);
						} else {
							for (int j = 0; j < limit; j++) {
								gpusConfig = new HashMap<String, Object>();
								gpusConfig.put("type", "GPU_Init");
								gpusConfig.put("token", agentToken);
								gpusConfig.put("uuid", g.getBoardId());
								gpusConfig.put("index", g.getModuleId());
								gpusConfig.put("sn", i);
								gpusConfig.put("time", System.currentTimeMillis());
								send(gpusConfig);
							}
						}
					}
					log.info(String.format(">>> RenderClient:graphics[%s]--Limit:[%s].", index, limit));
				}
			}
		}
	}

	public void sendGpuInfo(String newKey, String oldKey) {
		
		Map<String, Object> gpusConfig;
		gpusConfig = new HashMap<String, Object>();
		gpusConfig.put("type", "GPU_Info");
		gpusConfig.put("token", agentToken);
		gpusConfig.put("newKey", newKey);
		gpusConfig.put("oldKey", oldKey);
		gpusConfig.put("time", System.currentTimeMillis());
		send(gpusConfig);
		//log.info(String.format("-> RenderClient: Call sendGpuInfo{newKey:%s || oldKey:%s }.",newKey,oldKey));
	}

	public void sendOsInfo(OsInfo osInfo) {
		Map<String, Object> gpusConfig;
		gpusConfig = new HashMap<String, Object>();
		gpusConfig.put("type", "Os_Info");
		gpusConfig.put("token", agentToken);
		gpusConfig.put("osInfo", osInfo);
		gpusConfig.put("time", System.currentTimeMillis());
		send(gpusConfig);
		
	}
	
	public void sendStopOrder(String orderId) {
		Map<String, Object> gpusConfig;
		gpusConfig = new HashMap<String, Object>();
		gpusConfig.put("type", "Stop_Order");
		gpusConfig.put("token", agentToken);
		gpusConfig.put("orderId", orderId);
		gpusConfig.put("time", System.currentTimeMillis());
		send(gpusConfig);
		
	}

	public void send(Map<String, Object> messag) {
		if (channel != null && channel.isOpen() && channel.isActive()) {
			channel.writeAndFlush(new TextWebSocketFrame(JsonKit.toJson(messag)))
					.addListener(new ChannelFutureListener() {
						@Override
						public void operationComplete(ChannelFuture future) throws Exception {
							if (future.isSuccess()) {
								if(!StrKit.equals("Os_Info",messag.get("type").toString())) {
									log.info(String.format("-> Agent send a message:%s.",JsonKit.toJson(messag)));
								}
							} else {
								 log.info("-> Agent send a message failed.");
							}
						}
					});
		}
	}

	public void destroy() {
		isDestroy = true;
		Cirrus.isRenderClientConnectedCompleted = false;
		if (channel != null)
			channel.close();
		group.shutdownGracefully();
		Cirrus.isRenderClientConnected = false;
		Cirrus.isRenderClientConnectedCompleted = true;
	}

}
